<?php

declare(strict_types=1);

namespace App\Controller\Admin\Common;

use App\Entity\Goods;
use App\Entity\Order;
use App\Service\RedisService;
use App\Utils\Annotation\ApiName;
use App\Utils\Annotation\ApiParam;
use App\Utils\Traits\ControllerTrait;
use Doctrine\DBAL\LockMode;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Routing\Annotation\Route;

/**
 * Class OrderController
 * @package App\Controller\Admin\Common
 * @Route("/admin/common/order", name="admin-common-order-")
 * @ApiName(name="订单")
 */
class OrderController extends AbstractController {
    use ControllerTrait;

    protected $redis;

    public function __construct (RedisService $redis) {
        $this->redis = $redis->redis;
    }

    /**
     * @ApiName(name="lock组件，防止超卖"))
     * @ApiParam(name="goods_id", type="integer", comment="商品ID", require=true)
     * @ApiParam(name="number", type="integer", comment="数量", require=true)
     * @Route("/lock-factory", methods={"POST"}, name="lock-factory")
     * @param Request $request
     * @param LockFactory $lockFactory
     */
    public function lockFactory (Request $request, LockFactory $lock_factory) {
        $params = $this->onlyDeclare($request);
        $key = 'goods:id:'.$params['goods_id'];
        $lock = $lock_factory->createLock($key);
        if (!$lock->acquire()) {
            return json_failure('系统繁忙，稍后尝试');
        }
        $goods = $this->getDoctrine()->getRepository(Goods::class)->find($params['goods_id']);
        if (!$goods) {
            return json_failure('商品不存在');
        }
        if ($goods->getStock() == 0) {
            return json_failure('商品已售罄');
        }
        if ($goods->getStock() < $params['number']) {
            return json_failure('商品库存不足');
        }
        $this->getEntityManage()->beginTransaction();
        try {
            $order = new Order();
            $order->setGoodsId($goods->getId());
            $order->setNumber($params['number']);
            $goods->setStock($goods->getStock() - $params['number']);
            $this->getEntityManage()->persist($order);
            $this->getEntityManage()->persist($goods);
            $this->getEntityManage()->flush();
            $this->getEntityManage()->commit();
            return json_success('下单成功');
        } catch (\Exception $e) {
            $this->getEntityManage()->rollback();
            return json_failure('下单失败：'.$e->getMessage());
        } finally {
            $lock->release();
        }
    }

    /**
     * @ApiName(name="redis分布式锁，防止超卖")
     * @ApiParam(name="goods_id", type="integer", comment="商品ID", require=true)
     * @ApiParam(name="number", type="integer", comment="数量", require=true)
     * @Route("/redis-setnx", methods={"POST"}, name="redis-setnx")
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\JsonResponse
     * @throws \Exception
     */
    public function redisSetnx (Request $request) {
        $params = $this->onlyDeclare($request);
        $key = 'goods:id:'.$params['goods_id'];
        $result_goods_lock = $this->redis->set($key, 1, 'EX', 10, 'NX');
        if (!$result_goods_lock) {
            return json_failure('系统繁忙，稍后尝试');
        }
        $goods = $this->getDoctrine()->getRepository(Goods::class)->find($params['goods_id']);
        if (!$goods) {
            return json_failure('商品不存在');
        }
        if ($goods->getStock() == 0) {
            return json_failure('商品已售罄');
        }
        if ($goods->getStock() < $params['number']) {
            return json_failure('商品库存不足');
        }
        $this->getEntityManage()->beginTransaction();
        try {
            $order = new Order();
            $order->setGoodsId($goods->getId());
            $order->setNumber($params['number']);
            $goods->setStock($goods->getStock() - $params['number']);
            $this->getEntityManage()->persist($order);
            $this->getEntityManage()->persist($goods);
            $this->getEntityManage()->flush();
            $this->getEntityManage()->commit();
            return json_success('下单成功');
        } catch (\Exception $e) {
            $this->getEntityManage()->rollback();
            return json_failure('下单失败：'.$e->getMessage());
        } finally {
            $this->redis->del($key);
        }
    }

    /**
     * @ApiName(name="redis list 防止超卖", description="通过list实现防超卖时需要先向list里添加数据")
     * @ApiParam(name="goods_id", type="integer", comment="商品ID", require=true)
     * @ApiParam(name="number", type="integer", comment="数量", require=true)
     * @Route("/redis-list", methods={"POST"}, name="redis-list")
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\JsonResponse
     * @throws \Exception
     */
    public function redisList (Request $request) {
        $key = 'goods-list';
        if ($this->redis->lpop($key) === null) {
            return json_failure('商品已售罄');
        }
        $params = $this->onlyDeclare($request);
        $goods = $this->getDoctrine()->getRepository(Goods::class)->find($params['goods_id']);
        if (!$goods) {
            return json_failure('商品不存在');
        }
        if ($goods->getStock() == 0) {
            return json_failure('商品已售罄');
        }
        if ($goods->getStock() < $params['number']) {
            return json_failure('商品库存不足');
        }
        $this->getEntityManage()->beginTransaction();
        try {
            $goods = $this->getEntityManage()->find('App\Entity\Goods', $params['goods_id'], LockMode::PESSIMISTIC_WRITE);
            $order = new Order();
            $order->setGoodsId($goods->getId());
            $order->setNumber($params['number']);
            $goods->setStock($goods->getStock() - $params['number']);
            $this->getEntityManage()->persist($order);
            $this->getEntityManage()->persist($goods);
            $this->getEntityManage()->flush();
            $this->getEntityManage()->commit();
            return json_success('下单成功');
        } catch (\Exception $e) {
            $this->getEntityManage()->rollback();
            // 下单失败，将库存还回到redis list中
            $this->redis->rpush($key, 1);
            return json_failure('下单失败：'.$e->getMessage());
        }
    }
}